#include "vector.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

void vector_init(vector *v, size_t ele_size, vector_cons_fn cons_fn, vector_free_fn free_fn, vector_copy_fn copy_fn)
{
    v->element_size = ele_size;
    v->capacity = VECTOR_MIN_CAPACITY;
    v->size = 0;
    v->data = malloc(ele_size * v->capacity);
    assert(v->data != NULL);
    v->cons_fn = cons_fn;
    v->free_fn = free_fn; 
    v->copy_fn = copy_fn;
}

void vector_free(vector *v)
{
    if (v->free_fn)
    {
        for (size_t i = 0; i < v->size; i++)
        {
            void* addr = (char*)v->data + v->element_size * i;
            v->free_fn(addr);
        }
    }
    free(v->data); 
    v->capacity = v->size = 0;
    v->cons_fn = NULL;
    v->copy_fn = NULL;
    v->free_fn = NULL;
}

int vector_size(const vector *v)
{
    return v->size;
}

int vector_capacity(const vector *v)
{
    return v->capacity;
}

bool vector_empty(const vector *v)
{
    return v->size == 0;
}

void vector_realloc(vector* v, bool inc) {
    if (inc)
    {
        v->capacity *= 2;
    }
    else
    {
        if(v->capacity >= VECTOR_MIN_CAPACITY * 2) v->capacity /= 2;
        else v->capacity = VECTOR_MIN_CAPACITY;
    }
    v->data = realloc(v->data, v->capacity * v->element_size);
    assert(v->data);
}

void *vector_at(vector *v, int idx)
{
    assert(idx >= 0 && idx < v->size);

    return (char*)v->data + idx * v->element_size;
}

void vector_set(vector *v, int idx, const void *element)
{
    if (v->copy_fn)
    {
        v->copy_fn(element, vector_at(v, idx));
    }
    else
    {
        memcpy(vector_at(v, idx), element, v->element_size);
    }
}

void vector_push_back(vector *v, const void *element)
{
    if (v->size == v->capacity)
    {
        vector_realloc(v, true);        
    }
    void* dst = (char*)v->data + v->size * v->element_size;
    if (v->copy_fn)
    {
        v->copy_fn(element, dst);
    }
    else {
        memcpy(dst, element, v->element_size);            
    }
    v->size++;
}

void vector_pop_back(vector *v)
{
    assert(v->size > 0);

    void* dst = (char*)v->data + (v->size - 1) * v->element_size;
    if (v->free_fn)
    {
        v->free_fn(dst);
    }
    v->size--;
}

void vector_erase(vector *v, int idx)
{
    assert(idx >= 0);
    assert(idx < v->size);

    void* dst = (char*)v->data + idx * v->element_size;
    if (v->free_fn)
    {
        v->free_fn(dst);
    }
    size_t n_byte = (v->size - idx - 1) * v->element_size;
    memmove(dst, (char*)dst + v->element_size, n_byte);
    v->size--;    
}

void vector_clear(vector *v)
{
    if (v->free_fn)
    {
        for (size_t i = 0; i < v->size; i++)
        {
            void* addr = (char*)v->data + v->element_size * i;
            v->free_fn(addr);
        }
    }
    v->size = 0;
}

void vector_reserve(vector *v, size_t new_capacity)
{
    v->data = realloc(v->data, new_capacity * v->element_size);
    v->capacity = new_capacity;
}

void vector_resize(vector *v, size_t new_size)
{
    if (v->free_fn)
    {
        if (v->cons_fn)
        {
            for (size_t i = v->size; i < new_size; i++)
            {
                v->cons_fn((char*)v->data + v->element_size * i);
            }
        }
        for (size_t i = new_size; i < v->size; i++)
        {
            v->free_fn((char*)v->data + v->element_size * i);
        }
    }
    v->size = new_size;
}

void vector_shrink2fit(vector *v)
{
    size_t siz = v->size + 1 > VECTOR_MIN_CAPACITY ? v->size + 1 : VECTOR_MIN_CAPACITY;
    v->data = realloc(v->data, (siz) * v->element_size);
    assert(v->data);
    v->capacity = siz;
}

void vector_swap(vector *v, int idx1, int idx2)
{
    void *tmp = malloc(v->element_size);
    memcpy(tmp, vector_at(v, idx1), v->element_size);
    vector_set(v, idx1, vector_at(v, idx2));
    vector_set(v, idx2, tmp);
    free(tmp);
}
